package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.HotArticleService;

import com.heima.common.constants.ArticleConstants;
import com.heima.common.mess.ArticleVisitStreamMess;
import com.heima.feign.client.WmChannelFeginClient;
import com.heima.model.article.pojos.ApArticle;

import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.pojos.WmChannel;

import com.xxl.job.core.handler.annotation.XxlJob;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class HotArticleServiceImpl implements HotArticleService {

    @Resource
    private ApArticleMapper apArticleMapper;

    @Resource
    private WmChannelFeginClient wmChannelFeginClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 定时计算热点文章主方法
     * 由于当前方法是被定时任务执行，所有方法中没有参数
     */
    @Override
    @XxlJob("HotArticleJobHandle")
    public void computeHotArticle() {
        //1.查询前五天的文章列表
            //先获取前五天的日期
        //利用jdk中日历类来获取前五天的日期，如果之后的五天则写成-5
        /*Calendar calendar=Calendar.getInstance();
        calendar.add(Calendar.DATE,-5);
        Date dayParam=calendar.getTime();*/
        //第二种，采用org.joda.time.DateTime获取前五天的日期，5表示之前的五天，如果说是之后的五天，则写成-5
        Date dayParam= DateTime.now().minusDays(5).toDate();
        List<ApArticle> last5daysList = apArticleMapper.findArticleListByLast5days(dayParam);
        /**
         *  2.对这些文章进行分值计算
         *      权重配比
         *      阅读权重：1
         *      点赞权重：3
         *      评论权重：5
         *      收藏权重：8
         */
       List<HotArticleVo> scoreArticleList=jisuanArticleScore(last5daysList);

        //3.存储数据到redis
            //3.1 根据频道提取30条分值较高的热点文章保存到redis中
                //1.获取所有的频道
        ResponseResult responseResult = wmChannelFeginClient.list();
        if(responseResult.getCode()==200){
            //获取所有的频道实体类
           List<WmChannel> channelList= (List) responseResult.getData();
            //2.根据频道id去筛选每个频道下的热点文章
            for (WmChannel wmChannel : channelList) {
                //对scoreArticleList进行遍历并比较是否channelId是否一致,按照每个频道取出对应的数据
                List<HotArticleVo> channeIdHotArticleList = scoreArticleList.stream().
                        filter(hotArticleVo -> hotArticleVo.getChannelId().equals(wmChannel.getId())).
                        collect(Collectors.toList());
                //缓存到redis
                cacheToRedis(channeIdHotArticleList, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + wmChannel.getId());
            }
        }
        //3.2 根据推荐提取30条分值较高的热点文章保存到redis中
        cacheToRedis(scoreArticleList, ArticleConstants.HOT_ARTICLE_FIRST_PAGE+"__all__");
    }

    /**
     * 缓存数据 到redis 中
     * @param list
     * @param key
     */
    private void cacheToRedis(List<HotArticleVo> list, String key) {
        //按照分值进行降序排序
        list = list.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());
        //取出前三十条数据
        list = list.stream().limit(30).collect(Collectors.toList());
        //存入redis中
        stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(list));
    }

    /**
     * 抽取的方法之一，进行分值计算
     * @param last5daysList
     * @return 返回值list中是一定要包含这篇文章的总分值
     */
    private List<HotArticleVo> jisuanArticleScore(List<ApArticle> last5daysList) {

        List<HotArticleVo> hotArticleVoList=new ArrayList<>();

        //1.判断list中是否有数据
        if(last5daysList!=null && last5daysList.size()>0){
            //2.遍历
            for (ApArticle apArticle : last5daysList) {
                //3.实例化vo
                HotArticleVo vo=new HotArticleVo();
                BeanUtils.copyProperties(apArticle,vo);
                //4.计算每一篇文章的总分值
                Integer score=computeArticleScore(apArticle);
                vo.setScore(score);
                hotArticleVoList.add(vo);
            }
        }
        return hotArticleVoList;
    }

    /**
     * 抽取的第二个方法，计算每一篇文章的总分值
     * @param apArticle
     * @return
     */
    private Integer computeArticleScore(ApArticle apArticle) {
        Integer score=0;

       if(apArticle.getCollection()!=null){
           score+=apArticle.getCollection()* ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
       }
       if(apArticle.getComment()!=null){
           score+=apArticle.getComment()*ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
       }
       if(apArticle.getLikes()!=null){
           score+=apArticle.getLikes()*ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
       }
       if(apArticle.getViews()!=null){
           score+=apArticle.getViews();
       }
        return score;
    }

    /**
     * 更新文章的分值  同时更新缓存中的热点文章数据
     *
     * @param mess
     */
    @Override
    public void updateScore(ArticleVisitStreamMess mess) {
        //1.查询ap_article
        ApArticle apArticle = updateArticle(mess);

        //3.重新计算文章总分值
        Integer score = this.computeArticleScore(apArticle);
        score=score*3;

        //4.替换redis中的数据
        //根据频道来存储
        replaceRedis(mess, apArticle, score,ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());

        //根据推荐来存储
        replaceRedis(mess, apArticle, score,ArticleConstants.HOT_ARTICLE_FIRST_PAGE + "__all__");

    }

    /**
     * 抽取替换redis
     * @param mess
     * @param apArticle
     * @param score
     */
    private void replaceRedis(ArticleVisitStreamMess mess, ApArticle apArticle, Integer score,String key) {
        String hotArticlevoListStr = stringRedisTemplate.opsForValue().get(key);
        List<HotArticleVo> hotArticleVoList = JSONArray.parseArray(hotArticlevoListStr, HotArticleVo.class);
        boolean flag=true;
        if(hotArticleVoList!=null){
            for (HotArticleVo hotArticleVo : hotArticleVoList) {
                //如果缓存中存在
                if(hotArticleVo.getId().equals(mess.getArticleId())){
                    hotArticleVo.setScore(score);
                    flag=false;
                    break;
                }
            }
            //表示如果缓存中不存在
            if(flag){
                //判断list的长度
                if(hotArticleVoList.size()<30){
                    HotArticleVo hotArticleVo=new HotArticleVo();
                    BeanUtils.copyProperties(apArticle,hotArticleVo);
                    hotArticleVo.setScore(score);
                    hotArticleVoList.add(hotArticleVo);
                }else if(hotArticleVoList.size()>=30){//大于30条
                    //倒叙排序
                    hotArticleVoList = hotArticleVoList.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());
                    //取出最后一个元素
                    HotArticleVo lastHotArticlVo = hotArticleVoList.get(hotArticleVoList.size() - 1);
                    //比较分值
                    if(lastHotArticlVo.getScore()< score){
                        hotArticleVoList.remove(lastHotArticlVo);
                        HotArticleVo hot = new HotArticleVo();
                        BeanUtils.copyProperties(apArticle, hot);
                        hot.setScore(score);
                        hotArticleVoList.add(hot);
                    }
                }
                stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(hotArticleVoList));
            }
        }
    }

    /**
     * 2. 抽取的方法修改行为数量
     * @param mess
     * @return
     */
    private ApArticle updateArticle(ArticleVisitStreamMess mess) {
        //1.查询ap_article
        ApArticle apArticle = apArticleMapper.selectById(mess.getArticleId());
        if(apArticle!=null){
            //2.修改ap_article中行为数量
            apArticle.setCollection((apArticle.getCollection()==null?0:apArticle.getCollection())+ mess.getCollect());
            apArticle.setComment((apArticle.getComment()==null?0:apArticle.getComment())+ mess.getComment());
            apArticle.setViews((apArticle.getViews()==null?0:apArticle.getViews())+ mess.getView());
            apArticle.setLikes((apArticle.getLikes()==null?0:apArticle.getLikes())+ mess.getLike());
            apArticleMapper.updateById(apArticle);
            return apArticle;
        }
        return null;
    }
}
